iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

評測 Agent 的核心任務非常單純:讀題目的測試用例與學生的程式碼,推測哪幾段會過,並將結果分別餵給 UI 與導師 Agent。

這看似只是一次普通的 LLM 呼叫,但為了確保系統的穩定度與引導邏輯不被破壞,程式碼裡設計了嚴密的 5 步驟處理流程與「雙出口防漏」機制。

流程圖如下:

https://ithelp.ithome.com.tw/upload/images/20260925/201838763Z9mD7k8Wk.png

grade_code 的 5 個核心步驟:

在 agents/grader.py 裡,評測邏輯被拆成五個嚴謹的處理步驟:

1. 組 Prompt

將題目說明、entry_point、測試原文與學生的程式碼打包交給 Gemini。在 system_instruction 中直接下達硬約束:「你不會執行這些程式碼」、「不確定就當成不通過」,從源頭控制模型的推測邊界。

2. 呼叫 Gemini (要求 JSON)

利用 response_schema 強制要求模型輸出固定的 JSON 結構:

JSON

{
  "all_passed": true,
  "failed": ["assert find_value({}, 'x') == 'Not Found'"],
  "summary": "當字典為空時,未正確處理 KeyError。"
}

3. 失敗就降級 (關鍵的三態設計)

網路錯誤、API 回傳空白或不是合法 JSON 時,絕不拋出例外讓系統崩潰,而是回傳 parsed_ok = False 且 all_passed = None。

這裡的靈魂在於 None 的三態設計:

  • True:判斷為通過
  • False:判斷為不通過
  • None:沒有判斷(系統/解析失敗)

解析失敗時完全無法得知真實狀況,直接斷言成 False 也是一種虛假的宣稱。明確標示 None 能讓 UI 誠實顯示「這次沒有結果」,而不是給學生劃一個紅叉。

4. 過濾編造的測試

LLM 偶爾會產生幻覺,甚至在回答裡「憑空捏造」題目原本根本沒有寫的測試用例。程式在拿回結果後,會嚴格比對 problem["tests"] 的原文,只要對不上的字串一律丟棄,避免 UI 畫面上印出一段莫名其妙不存在的測試。

5. 從嚴判定 all_passed

判定標準為:all_passed = (旗標為真) and (len(failed) == 0)。
當模型自相矛盾時(例如回答了「全過」,清單裡卻列了失敗項),一律視為「沒過」。高報通過是最昂貴的錯誤——因為在第 3 期,一旦被判定為「全部通過」,系統會直接跳過導師 Agent、自動幫學生切換到下一題。

導師與 UI 拿到的東西不一樣:

評測算完之後會寫入 state["grade"],但這份資料送往兩個出口時,內容是有遮蔽的:

去處 拿到什麼 設計原理
左欄 UI 完整資料(含失敗的測試原文 assert ...) 學生需要清楚看到自己哪一行測試沒通過,方便改 code。
導師 Agent 只有 summary + 失敗數量 failed 裡面裝的是測試原文(例如 assert add(2, 3) == 5),裡面帶有預期輸出與解答細節。如果給導師看,導師極容易開後門把答案講出來。

這樣就可以避免導師直接拿到程式碼,並以程式碼去給予答案了

目前就先做到這樣,如果未來有什麼需要加強或更改的那就在來改進 :>


上一篇
Day 10 :沙盒預留
下一篇
Day 12 :模型問題
系列文
基於 Multi-Agent 協作之蘇格拉底式程式學習與自動化評測系統 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言